package com.monkeygroup.mygoplus.common.imageview;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Animatable;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.provider.MediaStore;
import android.support.annotation.Nullable;
import android.support.v4.util.LruCache;
import android.util.DisplayMetrics;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;

import com.facebook.common.util.UriUtil;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.controller.BaseControllerListener;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.drawee.view.SimpleDraweeView;
import com.facebook.imagepipeline.common.ResizeOptions;
import com.facebook.imagepipeline.common.RotationOptions;
import com.facebook.imagepipeline.image.ImageInfo;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.LinkedList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * ********图片加载类*********
 * ***思路：图片的加载在Adapter的getView()方法中执行，我们根据一个图片的URL到LruCache缓存中寻找Bitmap图片，如果找到则返回图片，
 * 如果找不到，则会根据URL产生一个Task并放到TaskQueue中，同时发送一个通知提醒后台轮询线程，轮询线程从TaskQueue中取出一个Task到线程池去执行（执行的是Task的run()方法，
 * 具体为：获得图片显示的实际大小；使用Options对图片进行压缩；加载图片且放入LruCache）
 * ***核心：Handler + Looper + Message（Android异步消息处理框架）
 * 当我们用Handler发送消息时，会把信息放到MessageQueue中，轮询线程会取出这条消息，交给Handler的handleMessage()方法进行处理
 */
public class ImageLoader {
    private static ImageLoader mInstance; // 实例
    private LruCache<String, Bitmap> mLruCache; // 图片缓存的核心类
    private ExecutorService mThreadPool; // 线程池
    private static final int DEFAULT_THREAD_COUNT = 1; // 线程池的线程数量，默认为
    private Type mType = Type.LIFO; // 任务队列的默认调度方式
    private LinkedList<Runnable> taskQueue; // 任务队列
    private Thread mPoolThread; // 后台轮询线程
    private Handler mPoolThreadHandler; // 与后台轮询线程绑定的Handler
    private Handler uiHandler; // 运行在UI线程的handler，用于给ImageView设置图片
    /**
     * 引入一个值为的信号量，防止mPoolThreadHander未初始化完成
     * Semaphore的作用是限制某一资源的同步访问
     * 可以把Semaphore理解成一个可以容纳N个人的房间，如果人数没有达到N就可以进去，如果人满了，就要等待有人出来才可以进去
     * 在addTask()方法中需要用到后台轮询线程poolThreadHandler，但存在线程同步问题，
     * 即addTask()方法可能在poolThreadHandler初始化之前就被调用了，所以我们需要定义这样一个“信号量”来调控线程同步
     */
    private volatile Semaphore mPoolThreadHandlerSemaphore = new Semaphore(0); // 控制addTask()方法在mPoolThreadHandler吃实话之后才能调用
    private volatile Semaphore mPoolSemaphore; // 引入一个值为的信号量，由于线程池内部也有一个阻塞线程，防止加入任务的速度过快，使LIFO效果不明显

    /**
     * 图片加载的策略（FIFO先进先出、LIFO后进先出）
     */
    public enum Type {
        FIFO, LIFO
    }

    /**
     * 构造函数
     * 由于ImageLoader中需要使用LruCache来缓存图片，需要占据较大的空间，所以整个项目中只需要一个ImageLoader即可（需要使用单例模式）
     * 因此我们把构造方法设为private权限，防止用户new出实例
     *
     * @param threadCount 任务队列中默认线程数
     * @param type        图片加载策略（先进先出/后进先出）
     */
    private ImageLoader(int threadCount, Type type) {
        init(threadCount, type);
    }

    /**
     * 单例获得该实例对象（无参数，按照默认值进行初始化）
     */
    public static ImageLoader getInstance() {
        if (mInstance == null) {
            synchronized (ImageLoader.class) {
                if (mInstance == null) {
                    mInstance = new ImageLoader(DEFAULT_THREAD_COUNT, Type.LIFO);
                }
            }
        }
        return mInstance;
    }

    /**
     * 单例获得该实例对象（有参数，用户可以根据实际需要对ImageLoader进行实例化）
     */
    public static ImageLoader getInstance(int threadCount, Type type) {
        if (mInstance == null) {
            synchronized (ImageLoader.class) {
                if (mInstance == null) {
                    mInstance = new ImageLoader(threadCount, type);
                }
            }
        }
        return mInstance;
    }

    /**
     * 完成成员变量的初始化操作
     */
    private void init(int threadCount, Type type) {
        /**
         * 初始化后台轮询线程（使用Looper、Handler、Message实现）
         */
        mPoolThread = new Thread() {
            @Override
            public void run() {
                Looper.prepare();
                mPoolThreadHandler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        mThreadPool.execute(getTask()); // 线程池取出一个任务去执行
                        try {
                            mPoolSemaphore.acquire();
                        } catch (InterruptedException e) {
                        }
                    }
                };
                mPoolThreadHandlerSemaphore.release(); // 释放一个信号量
                Looper.loop(); // 开始无限循环
            }
        };
        mPoolThread.start();
        /**
         * 初始化LruCache
         */
        int maxMemory = (int) Runtime.getRuntime().maxMemory(); // 获取应用程序最大可用内存
        int cacheSize = maxMemory /8; // 设置缓存的存储空间是总空间的/
        mLruCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight(); // getRowBytes()获取到图片每行有多少字节，乘以图片的高度就是图片占据的内存
            }
        };
        /**
         * 初始化其他
         */
        mThreadPool = Executors.newFixedThreadPool(threadCount); // 初始化线程池threadPool
        mPoolSemaphore = new Semaphore(threadCount); // 初始化线程池（消息队列）信号量
        taskQueue = new LinkedList<Runnable>(); // 初始化任务队列taskQueue
        mType = type == null ? Type.LIFO : type; // 同步Type
    }

    /**
     * 根据路径path找到对应的图片并异步加载到主界面中
     *
     * @param path      图片的路径
     * @param imageView 目标的ImageView（图片将要被加载到的ImageView）
     */
    public void loadImage(final String path, final ImageView imageView) {
        imageView.setTag(path); // 将PATH设置为imageView的TAG，方便在主线程中对比、设置图片
        // UI线程
        if (uiHandler == null) {
            uiHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    ImgBeanHolder holder = (ImgBeanHolder) msg.obj;
                    ImageView imageView = holder.imageView;
                    Bitmap bm = holder.bitmap;
                    String path = holder.path;
                    // 将path与imageView的tag进行比较，如果相同，则为imageView设置图片
                    if (imageView.getTag().toString().equals(path)) {
                        imageView.setImageBitmap(bm);
                    }
                }
            };
        }

        Bitmap bm = getBitmapFromLruCache(path); // 根据路径Path在缓存中获取Bitmap
        if (bm != null) { // 如果这张图片存在于缓存中，则通知UI线程更新图片
            ImgBeanHolder holder = new ImgBeanHolder();
            holder.bitmap = bm;
            holder.imageView = imageView;
            holder.path = path;
            Message message = Message.obtain();
            message.obj = holder;
            uiHandler.sendMessage(message);
        } else { // 如果这张图片没有存在于缓存中，则添加一个新的任务到任务队列中
            addTask(new Runnable() { // 添加一个任务到任务队列中
                @Override
                public void run() {
                    /**
                     * 加载图片
                     */
                    // 压缩图片：、获取图片需要显示的大小
                    ImageSize imageSize = getImageViewSize(imageView);
                    // 压缩图片：、压缩图片
                    int reqWidth = imageSize.width;
                    int reqHeight = imageSize.height;
                    Bitmap bm = decodeSampledBitmapFromResource(path, reqWidth, reqHeight);
                    // 压缩图片：、将图片加入到缓存
                    addBitmapToLruCache(path, bm);
                    // 将上面操作获取到的数据封装到ImgBeanHolder实体对象中，通知UI线程处理
                    ImgBeanHolder holder = new ImgBeanHolder();
                    holder.bitmap = getBitmapFromLruCache(path);
                    holder.imageView = imageView;
                    holder.path = path;
                    Message message = Message.obtain();
                    message.obj = holder;
                    uiHandler.sendMessage(message);
                    mPoolSemaphore.release(); // 为线程池释放一个信号量
                }
            });
        }
    }

    /**
     * 添加一个任务到任务队列
     * synchronized是为了避免多个线程同时到达这个方法中申请信号量而发生死锁
     */
    private synchronized void addTask(Runnable runnable) {
        /**
         * 请求资源信号量，避免死锁
         * 如果还没有对mPoolThreadHandler进行初始化，则默认的房间里可以容纳个人，所以如果此时addTask()方法请求资源会被阻塞
         * 通过这个信号量控制addTask()方法必须在mPoolThreadHandler初始化之后才调用
         */
        try {
            if (mPoolThreadHandler == null)
                mPoolThreadHandlerSemaphore.acquire(); // 如果当前mPoolThreadHandler还没有初始化，则该线程阻塞（等待），直到mPoolThreadHandler初始化
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        taskQueue.add(runnable); // 添加Runnable任务到任务队列
        mPoolThreadHandler.sendEmptyMessage(0x110); // 发送一个通知，通知后台轮询线程，x是一个随意的值
    }

    /**
     * 从任务队列中取出一个任务
     * 根据为ImageLoader实例设定的图片加载策略决定是取出最后一个还是取出第一个
     */
    private synchronized Runnable getTask() {
        if (mType == Type.FIFO) {
            return taskQueue.removeFirst();
        } else if (mType == Type.LIFO) {
            return taskQueue.removeLast();
        }
        return null;
    }

    /**
     * 根据ImageView获得适当的压缩的目标宽和高
     */
    private ImageSize getImageViewSize(ImageView imageView) {
        /**
         * 获取ImageView的LayoutParams
         */
        final DisplayMetrics metrics = imageView.getContext().getResources().getDisplayMetrics();
        final LayoutParams lp = imageView.getLayoutParams();
        /**
         * 定义ImageView显示的宽度
         */
        int width = lp.width == LayoutParams.WRAP_CONTENT ?  0: imageView.getWidth(); // 获取ImageView的实际宽度
        if (width <= 0) // ImageView可能是刚new出来就来执行这个方法，所以还没有宽高值，只能通过在layout中声明的宽高值来赋值
            width = lp.width; // 获取ImageView在layout中声明的宽度
        if (width <= 0) // 在layout中设置的宽高值是WRAP_CONTENT或MATCH_PARENT，则还是取出，这时我们就需要看ImageView有没有设置max值
            width = getImageViewFieldValue(imageView, "mMaxWidth"); // 通过反射获取ImageView的宽度最大值
        if (width <= 0) // 可能ImageView没有设置max值，因此我们只能设置ImageView的宽或高为屏幕的宽或高
            width = metrics.widthPixels;
        /**
         * 定义ImageView显示的高度（同宽度）
         */
        int height = lp.height == LayoutParams.WRAP_CONTENT ?  0: imageView.getHeight();
        if (height <= 0) height = lp.height;
        if (height <= 0) height = getImageViewFieldValue(imageView, "mMaxHeight");
        if (height <= 0) height = metrics.heightPixels;
        /**
         * 将ImageView压缩后的宽和高封装到ImageSize实体类中返回
         */
        ImageSize imageSize = new ImageSize();
        imageSize.width = width;
        imageSize.height = height;
        return imageSize;
    }

    /**
     * 从LruCache中获取一张图片，如果不存在就返回null
     */
    private Bitmap getBitmapFromLruCache(String key) {
        return mLruCache.get(key);
    }

    /**
     * 将图片添加到LruCache缓存
     *
     * @param path   路径，先要判断图片是否已经在缓存中
     * @param bitmap 图片
     */
    private void addBitmapToLruCache(String path, Bitmap bitmap) {
        if (getBitmapFromLruCache(path) == null) {
            if (bitmap != null)
                mLruCache.put(path, bitmap);
        }
    }

    /**
     * 根据图片需求的宽高和图片实际的宽高计算inSampleSize（图片压缩的比例，用于压缩图片）
     *
     * @param options   图片实际的宽和高
     * @param reqWidth  图片需要的宽度
     * @param reqHeight 图片需要的高度
     */
    private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        int width = options.outWidth; // 原图片的宽度
        int height = options.outHeight; // 原图片的高度
        int inSampleSize = 1; // 缩放的比例
        if (width > reqWidth && height > reqHeight) {
            int widthRatio = Math.round((float) width / (float) reqWidth); // 计算出实际宽度和目标宽度的比率
            int heightRatio = Math.round((float) width / (float) reqWidth);
            inSampleSize = Math.max(widthRatio, heightRatio); // 取宽度缩放值和高度缩放值的较大值作为图片缩放比例
        }
        return inSampleSize;
    }

    /**
     * 根据图片需要显示的宽和高对图片进行压缩
     *
     * @param path      图片的路径
     * @param reqWidth  图片显示的宽度
     * @param reqHeight 图片显示的高度
     */
    private Bitmap decodeSampledBitmapFromResource(String path, int reqWidth, int reqHeight) {
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true; // 如果该值设为true那么将不返回实际的bitmap，也不给其分配内存空间（避免内存溢出），但允许我们查询图片信息（包括图片大小信息）
        BitmapFactory.decodeFile(path, options); // 经过这行代码，options就获得了图片实际的宽和高
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // 调用上面定义的方法计算inSampleSize值（图片压缩的比例）
        options.inJustDecodeBounds = false; // 设置可以取出图片
        Bitmap bitmap = BitmapFactory.decodeFile(path, options); // 使用获取到的inSampleSize值再次解析图片，取出Bitmap
        return bitmap;
    }

    /**
     * 存放图片的Bitmap源、ImageView和图片路径的实体类
     */
    private class ImgBeanHolder {
        Bitmap bitmap;
        ImageView imageView;
        String path;
    }

    /**
     * 存放ImageView显示的宽高的实体类
     */
    private class ImageSize {
        int width; // ImageView显示的宽度
        int height; // ImageView显示的高度
    }

    /**
     * 通过反射获取某个Object的fieldName属性对应的值（本程序中是通过反射获得ImageView设置的最大宽度和高度）
     */
    private static int getImageViewFieldValue(Object object, String fieldName) {
        int value = 0;
        try {
            Field field = ImageView.class.getDeclaredField(fieldName);
            field.setAccessible(true);
            int fieldValue = (Integer) field.get(object);
            if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) {
                value = fieldValue;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return value;
    }

    /**
     * 加載本地圖片時
     * @param draweeView
     * @param filePath
     * @param reqWidth
     * @param reqHeight
     */
    public static void loadFile(final SimpleDraweeView draweeView, String filePath, final int reqWidth, final int reqHeight) {
        returnBitMap(filePath);

        Uri uri = new Uri.Builder().scheme(UriUtil.LOCAL_FILE_SCHEME).path(filePath).build();

        ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri).setRotationOptions(RotationOptions.autoRotate()).setLocalThumbnailPreviewsEnabled(true).setResizeOptions(new ResizeOptions(reqWidth, reqHeight)).build();
        DraweeController controller = Fresco.newDraweeControllerBuilder().setImageRequest(request).setOldController(draweeView.getController()).setControllerListener(new BaseControllerListener<ImageInfo>() {
            @Override
            public void onFinalImageSet(String id, @Nullable ImageInfo imageInfo, @Nullable Animatable anim) {
                if (imageInfo == null) {
                    return;
                }
                ViewGroup.LayoutParams vp = draweeView.getLayoutParams();
                vp.width = reqWidth;
                vp.height = reqHeight;
                draweeView.requestLayout();
            }
        }) .build();
        draweeView.setController(controller);
    }

    /**
     * return a bitmap from service
     * @param url
     * @return bitmap type
     */
    public final static Bitmap returnBitMap(String url) {
        URL myFileUrl = null;
        Bitmap bitmap = null;

        try {
            myFileUrl = new URL(url);
            HttpURLConnection conn;

            conn = (HttpURLConnection) myFileUrl.openConnection();

            conn.setDoInput(true);
            conn.connect();
            InputStream is = conn.getInputStream();
            bitmap = BitmapFactory.decodeStream(is);

        } catch (MalformedURLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }  catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return bitmap;
    }
}


